// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#pragma once

#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <type_traits>
#include <utility>

namespace fastfeed {

enum class PriceMapSide { ASK, BID };  // enum class PriceMapSide

template <typename T, PriceMapSide Side>
class Int64PriceMap {
 public:
  using Comparer = typename std::
      conditional<Side == PriceMapSide::BID, std::greater<int64_t>, std::less<int64_t>>::type;
  using Map = std::map<int64_t, T, Comparer>;
  using Iterator = typename Map::iterator;
  using ConstIterator = typename Map::const_iterator;

  Int64PriceMap() {}
  Int64PriceMap(const Int64PriceMap& other) : map_(other.map_) {}
  ~Int64PriceMap() {}

  bool empty() const;
  int32_t depth() const;
  std::optional<int64_t> min_price() const;
  std::optional<int64_t> max_price() const;

  T* GetLevel(int64_t price, bool next_if_not_exist = false);
  const T* GetLevel(int64_t price, bool next_if_not_exist = false) const;

  void SetLevel(int64_t price, const T& t);
  template <typename... Args>
  void EmplaceLevel(int64_t price, Args&&... args);

  void EraseLevel(int64_t price);
  void Clear();

  Iterator FindLevel(int64_t price, bool return_next_level_if_not_exist = false);
  ConstIterator FindLevel(int64_t price, bool return_next_level_if_not_exist = false) const;

  // These methods start with small small-case letter to be used in range-based loop.
  Iterator begin() { return map_.begin(); }
  ConstIterator begin() const { return map_.begin(); }
  Iterator end() { return map_.end(); }
  ConstIterator end() const { return map_.end(); }

  void EraseUntilBound(int64_t upper_bound) {
    // from inside, thus upper bound
    map_.erase(map_.begin(), map_.upper_bound(upper_bound));
  }

 private:
  Map map_;
};  // class Int64PriceMap

template <typename T>
using AskInt64PriceMap = Int64PriceMap<T, PriceMapSide::ASK>;

template <typename T>
using BidInt64PriceMap = Int64PriceMap<T, PriceMapSide::BID>;

template <typename T>
using Int64PriceMapIterator = typename AskInt64PriceMap<T>::Iterator;

template <typename T>
using Int64PriceMapConstIterator = typename AskInt64PriceMap<T>::ConstIterator;

template <typename T, PriceMapSide Side>
bool Int64PriceMap<T, Side>::empty() const {
  return map_.empty();
}

template <typename T, PriceMapSide Side>
int32_t Int64PriceMap<T, Side>::depth() const {
  return map_.size();
}

template <typename T, PriceMapSide Side>
std::optional<int64_t> Int64PriceMap<T, Side>::min_price() const {
  if (empty()) {
    return std::nullopt;
  }
  if (Side == PriceMapSide::ASK) {  // `if` will be removed in optimization.
    return map_.begin()->first;
  } else {
    return map_.rbegin()->first;
  }
}

template <typename T, PriceMapSide Side>
std::optional<int64_t> Int64PriceMap<T, Side>::max_price() const {
  if (empty()) {
    return std::nullopt;
  }
  if (Side == PriceMapSide::ASK) {  // `if` will be removed in optimization.
    return map_.rbegin()->first;
  } else {
    return map_.begin()->first;
  }
}

template <typename T, PriceMapSide Side>
typename Int64PriceMap<T, Side>::Iterator Int64PriceMap<T, Side>::FindLevel(
    int64_t price,
    bool return_next_level_if_not_exist) {
  if (!return_next_level_if_not_exist) {
    return map_.find(price);
  } else {
    return map_.lower_bound(price);
  }
}

template <typename T, PriceMapSide Side>
typename Int64PriceMap<T, Side>::ConstIterator Int64PriceMap<T, Side>::FindLevel(
    int64_t price,
    bool return_next_level_if_not_exist) const {
  if (!return_next_level_if_not_exist) {
    return map_.find(price);
  } else {
    return map_.lower_bound(price);
  }
}

template <typename T, PriceMapSide Side>
T* Int64PriceMap<T, Side>::GetLevel(int64_t price, bool return_next_level_if_not_exist) {
  auto it = FindLevel(price, return_next_level_if_not_exist);
  if (it == map_.end()) {
    return nullptr;
  }
  return &it->second;
}

template <typename T, PriceMapSide Side>
const T* Int64PriceMap<T, Side>::GetLevel(int64_t price, bool return_next_level_if_not_exist)
    const {
  auto it = FindLevel(price, return_next_level_if_not_exist);
  if (it == map_.end()) {
    return nullptr;
  }
  return &it->second;
}

template <typename T, PriceMapSide Side>
void Int64PriceMap<T, Side>::SetLevel(int64_t price, const T& t) {
  map_[price] = t;
}

template <typename T, PriceMapSide Side>
template <typename... Args>
void Int64PriceMap<T, Side>::EmplaceLevel(int64_t price, Args&&... args) {
  map_.emplace(price, std::forward<Args>(args)...);
}

template <typename T, PriceMapSide Side>
void Int64PriceMap<T, Side>::EraseLevel(int64_t price) {
  map_.erase(price);
}

template <typename T, PriceMapSide Side>
void Int64PriceMap<T, Side>::Clear() {
  map_.clear();
}

}  // namespace fastfeed
